import { EditOutlined } from '@ant-design/icons';
import { Graph, Shape } from '@antv/x6';
import { Clipboard } from '@antv/x6-plugin-clipboard';
import { Export } from '@antv/x6-plugin-export';
import { History } from '@antv/x6-plugin-history';
import { Keyboard } from '@antv/x6-plugin-keyboard';
import { Selection } from '@antv/x6-plugin-selection';
import { Snapline } from '@antv/x6-plugin-snapline';
import { Stencil } from '@antv/x6-plugin-stencil';
import { Transform } from '@antv/x6-plugin-transform';
import { javascript } from '@codemirror/lang-javascript';
import CodeMirror from '@uiw/react-codemirror';
import { Button, Checkbox, Divider, Form, Input, InputNumber, Select, Space, theme, } from 'antd';
import { useLayoutEffect, useReducer, useRef } from 'react';
import { v4 as uuidv4 } from 'uuid';
import PopupSelect from '../../components/PopupSelect';
import SearchTable from '../../components/SearchTable';
import useModal from '../../components/useModal';
import alert from '../../utils/alert';
import client from "../../utils/client";
import styles from './editor.module.css';

const { useToken } = theme;
const TEMPLATE_SCRIPT = `function (ctx) {
}`;

export default function Editor() {
  const content_container = useRef();
  const stencil_container = useRef();
  const graph_ref = useRef();
  const data_ref = useRef({ nodes: [], edges: [] });
  const { token } = useToken();
  const [form_workflow] = Form.useForm();
  const [form_node] = Form.useForm();
  const [form_edge] = Form.useForm();
  const [modal, modalHolder] = useModal();

  const wf_ID = Form.useWatch('ID', form_workflow);
  const wf_名称 = Form.useWatch('名称', form_workflow);
  const wf_数据卡片 = Form.useWatch('数据卡片', form_workflow);

  const [state, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'selection:changed': return { ...state, selected: action.selected };
      case 'history:change': return { ...state, canUndo: action.canUndo, canRedo: action.canRedo };
      default:
        return state;
    }
  }, {
    selected: null,
    canUndo: false,
    canRedo: false,
  });

  // 注册节点类型
  function registerNode() {
    const ports = {
      groups: {
        top: {
          position: 'top',
          attrs: {
            circle: {
              r: 4,
              magnet: true,
              stroke: '#5F95FF',
              strokeWidth: 1,
              fill: '#fff',
              style: {
                visibility: 'hidden',
              },
            },
          },
        },
        right: {
          position: 'right',
          attrs: {
            circle: {
              r: 4,
              magnet: true,
              stroke: '#5F95FF',
              strokeWidth: 1,
              fill: '#fff',
              style: {
                visibility: 'hidden',
              },
            },
          },
        },
        bottom: {
          position: 'bottom',
          attrs: {
            circle: {
              r: 4,
              magnet: true,
              stroke: '#5F95FF',
              strokeWidth: 1,
              fill: '#fff',
              style: {
                visibility: 'hidden',
              },
            },
          },
        },
        left: {
          position: 'left',
          attrs: {
            circle: {
              r: 4,
              magnet: true,
              stroke: '#5F95FF',
              strokeWidth: 1,
              fill: '#fff',
              style: {
                visibility: 'hidden',
              },
            },
          },
        },
      },
      items: [
        {
          group: 'top',
        },
        {
          group: 'right',
        },
        {
          group: 'bottom',
        },
        {
          group: 'left',
        },
      ],
    }

    // 开始
    Graph.registerNode(
      'node-1',
      {
        inherit: 'circle',
        label: '开始',
        width: 40,
        height: 40,
        attrs: {
          body: {
            strokeWidth: 1,
            stroke: 'green',
            fill: '#EFF4FF',
          },
          text: {
            fontSize: 12,
            fill: '#262626',
          },
        },
        ports: { ...ports },
        data: {
          类型: 1,
          名称: '开始',
          状态描述: '待提交',
        },
      },
      true,
    );

    // 结束
    Graph.registerNode(
      'node-2',
      {
        inherit: 'circle',
        label: '结束',
        width: 40,
        height: 40,
        attrs: {
          body: {
            strokeWidth: 1,
            stroke: '#CCCCCC',
            fill: '#EFF4FF',
          },
          text: {
            fontSize: 12,
            fill: '#262626',
          },
        },
        ports: { ...ports },
        data: {
          类型: 2,
          名称: '结束',
          状态描述: '已结束',
        },
      },
      true,
    );

    // 审批
    Graph.registerNode(
      'node-3',
      {
        inherit: 'rect',
        label: '审批',
        attrs: {
          body: {
            strokeWidth: 1,
            stroke: '#5F95FF',
            fill: '#EFF4FF',
          },
          text: {
            fontSize: 12,
            fill: '#262626',
          },
        },
        ports: { ...ports },
        data: {
          类型: 3,
          名称: '审批',
          状态描述: '待审批',
        },
      },
      true,
    );

    // 会签
    Graph.registerNode(
      'node-4',
      {
        inherit: 'rect',
        label: '会签',
        attrs: {
          body: {
            strokeWidth: 1,
            stroke: '#5F95FF',
            fill: '#EFF4FF',
            rx: 10,
            ry: 10,
          },
          text: {
            fontSize: 12,
            fill: '#262626',
          },
        },
        ports: { ...ports },
        data: {
          类型: 4,
          名称: '会签',
          状态描述: '待会签',
        },
      },
      true,
    );

    // 虚拟
    Graph.registerNode(
      'node-5',
      {
        inherit: 'circle',
        label: '虚拟',
        width: 40,
        height: 40,
        attrs: {
          body: {
            strokeWidth: 1,
            stroke: '#5F95FF',
            fill: '#EFF4FF',
          },
          text: {
            fontSize: 12,
            fill: '#262626',
          },
        },
        ports: { ...ports },
        data: {
          类型: 5,
          名称: '虚拟',
          状态描述: '虚拟',
        },
      },
      true,
    );
  }

  // 初始化画布
  function initGraph() {
    registerNode();

    const graph = new Graph({
      container: content_container.current,
      // autoResize: true, // 开启后会导致无法居中
      grid: {
        visible: true,
        type: 'doubleMesh',
        args: [
          {
            color: '#eee', // 主网格线颜色
            thickness: 0.5, // 主网格线宽度
          },
          {
            color: '#ddd', // 次网格线颜色
            thickness: 0.5, // 次网格线宽度
            factor: 4, // 主次网格线间隔
          },
        ],
      },
      panning: {
        enabled: true,
        modifiers: 'ctrl',
      },
      mousewheel: {
        enabled: true,
        modifiers: 'ctrl',
        minScale: 0.5,
        maxScale: 3,
      },
      connecting: {
        router: 'manhattan',
        connector: {
          name: 'rounded',
          args: {
            radius: 8,
          },
        },
        anchor: 'center',
        connectionPoint: 'anchor',
        allowLoop: false,
        allowBlank: false,
        allowMulti: false,
        allowNode: false,
        snap: {
          radius: 20,
        },
        createEdge() {
          return new Shape.Edge({
            attrs: {
              line: {
                stroke: '#A2B1C3',
                strokeWidth: 2,
                targetMarker: {
                  name: 'block',
                  width: 12,
                  height: 8,
                },
              },
            },
            defaultLabel: {
              markup: [
                {
                  tagName: 'rect',
                  selector: 'body',
                },
                {
                  tagName: 'text',
                  selector: 'label',
                },
              ],
              attrs: {
                text: {
                  fill: '#262626',
                  fontSize: 12,
                  textAnchor: 'middle',
                  textVerticalAnchor: 'middle',
                  pointerEvents: 'none',
                },
                rect: {
                  ref: 'label',
                  fill: 'transparent',
                  rx: 3,
                  ry: 3,
                  refWidth: 1,
                  refHeight: 1,
                  refX: 0,
                  refY: 0,
                },
              },
              position: {
                distance: 0.5,
                offset: -10,
                options: {
                  keepGradient: true,
                },
              },
            },
            data: {},
            zIndex: 0,
          });
        },
      },
      highlighting: {
        default: {
          name: 'stroke',
          args: {
            attrs: {
              stroke: '#5F95FF',
            },
          },
        },
      },
    })
      .use(
        new Transform({
          resizing: {
            enabled: true,
            minWidth: 40,
            maxWidth: 300,
            minHeight: 20,
            maxHeight: 100,
            allowReverse: false,
          },
        }),
      )
      .use(
        new Selection({
          rubberband: true,
          strict: true,
          showNodeSelectionBox: true,
          showEdgeSelectionBox: false,
          pointerEvents: 'none',
        }),
      )
      .use(new Snapline())
      .use(new Keyboard())
      .use(new Clipboard())
      .use(new History({
        beforeAddCommand(event, args) {
          // 避免边的选中效果进入撤销历史
          if (args.cell.shape === 'edge' && (args.key === 'tools' || args.key === "attrs")) return false;
        }
      }))
      .use(new Export());

    // 控制连接桩显示/隐藏
    const node_points_show = () => {
      const ports = content_container.current.querySelectorAll('.x6-port-body');
      ports.forEach(port => {
        port.style.visibility = 'visible';
      });
    };
    const node_points_hide = () => {
      const ports = content_container.current.querySelectorAll('.x6-port-body');
      ports.forEach(port => {
        port.style.visibility = 'hidden';
      });
    };
    graph.on('node:mouseenter', node_points_show);
    graph.on('node:mouseleave', node_points_hide);

    // 控制边的连接点显示/隐藏
    graph.on('edge:mouseenter', ({ cell }) => {
      cell.addTools([
        {
          name: 'source-arrowhead',
          args: {
            attrs: {
              d: 'M 8 0 m -8 0 a 8 8 0 1 0 16 0 a 8 8 0 1 0 -16 0',
              fill: '#A2B1C3',
            },
          },
        },
        {
          name: 'target-arrowhead',
          args: {
            attrs: {
              fill: '#A2B1C3',
            },
          },
        },
      ]);
      node_points_show();
    });
    graph.on('edge:mouseleave', ({ cell }) => {
      cell.removeTools();
      node_points_hide();
    });

    // 边的选中状态
    graph.on('edge:selected', ({ edge }) => {
      edge.setAttrs({
        line: { stroke: '#feb663', strokeWidth: 4 },
      });
    });
    graph.on('edge:unselected', ({ edge }) => {
      edge.setAttrs({
        line: { stroke: '#A2B1C3', strokeWidth: 2 },
      });
    });

    // 更新是否可撤销和可恢复的状态
    graph.on('history:change', () => dispatch({
      type: 'history:change',
      canRedo: graph.canRedo(),
      canUndo: graph.canUndo(),
    }));

    // 选中变化联动更新状态
    graph.on('selection:changed', ({ selected }) => {
      if (selected.length === 1) {
        if (selected[0].isNode()) {
          form_node.resetFields();
          form_node.setFieldsValue(selected[0].data);
        }
        if (selected[0].isEdge()) {
          form_edge.resetFields();
          form_edge.setFieldsValue(selected[0].data);
        }
        dispatch({ type: 'selection:changed', selected: selected[0] });
      } else {
        dispatch({ type: 'selection:changed', selected: null });
      }
    });

    // 修复X6选中到边后，节点失去选中但是不清除缩放框的bug
    graph.on('node:unselected', () => graph.clearTransformWidgets());

    return graph;
  }

  // 初始化元件面板
  function createStencil(graph) {
    const stencil = new Stencil({
      target: graph,
      stencilGraphWidth: 120,
      stencilGraphHeight: 0,
      layoutOptions: {
        columns: 1,
        columnWidth: 100
      },
      groups: [
        {
          name: 'group0',
          title: '元件库',
          collapsable: false,
        }
      ],
    });

    const n0 = graph.createNode({
      shape: 'node-1',
    });

    const n1 = graph.createNode({
      shape: 'node-2',
    });
    const n2 = graph.createNode({
      shape: 'node-3',
      width: 100,
      height: 40,
    });
    const n3 = graph.createNode({
      shape: 'node-4',
      width: 100,
      height: 40,
    });
    const n4 = graph.createNode({
      shape: 'node-5',
    });
    stencil.load([n0, n2, n3, n4, n1], 'group0');
    stencil_container.current.appendChild(stencil.container);
  }

  // 绑定快捷键
  function bindKey(graph) {
    // copy paste
    graph.bindKey(['meta+c', 'ctrl+c'], () => {
      const cells = graph.getSelectedCells();
      if (cells.length) {
        graph.copy(cells);
      }
    });
    graph.bindKey(['meta+x', 'ctrl+x'], () => {
      const cells = graph.getSelectedCells();
      if (cells.length) {
        graph.cut(cells);
      }
    });
    graph.bindKey(['meta+v', 'ctrl+v'], () => {
      if (!graph.isClipboardEmpty()) {
        const cells = graph.paste({ offset: 20 });
        graph.cleanSelection();
        graph.select(cells);
      }
    });

    // undo redo
    graph.bindKey(['meta+z', 'ctrl+z'], () => {
      if (graph.canUndo()) {
        graph.undo();
      }
    });
    graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
      if (graph.canRedo()) {
        graph.redo();
      }
    });

    // select all
    graph.bindKey(['meta+a', 'ctrl+a'], () => {
      const nodes = graph.getNodes();
      if (nodes) {
        graph.select(nodes);
      }
    });

    // delete
    graph.bindKey(['backspace', 'del'], () => {
      const cells = graph.getSelectedCells();
      if (cells.length) {
        graph.removeCells(cells);
      }
    });
  }

  useLayoutEffect(() => {
    if (graph_ref.current) return; // 避免重复初始化
    graph_ref.current = initGraph();
    createStencil(graph_ref.current);
    bindKey(graph_ref.current);
    // eslint-disable-next-line
  }, []);

  function reset() {
    graph_ref.current.cleanHistory();
    graph_ref.current.fromJSON({});
    data_ref.current = { nodes: [], edges: [] };
    form_workflow.resetFields();
  }

  function open(clone) {
    let selected = null;
    const handle = modal.open({
      title: '请选择流程',
      width: '50%',
      content: <SearchTable placeholder="搜索流程名称"
        columns={[{
          title: '名称',
          dataIndex: '名称',
          width: 200,
        }, {
          title: '版本号',
          dataIndex: '版本号',
          width: 150,
        }, {
          title: '启用标记',
          dataIndex: '启用标记',
          width: 150,
          render: v => v ? '已启用' : '未启用'
        }]}
        onLoad={(queryValue, queryOrder, pageNo, pageSize) => {
          return client.job('CardR', {
            Card: 'B.流程定义',
            Model: ['名称', '版本号', '启用标记', '动态标记'],
            Filter: [['名称', 'like', queryValue], { or: [['动态标记', '=', false], ['动态标记', 'is', null]] }],
            Order: { '名称': 'asc', '版本号': 'desc' },
            PageSize: pageSize,
            PageNo: pageNo,
          });
        }}
        onSelect={record => selected = record}
        onSelectEnd={(record = {}) => {
          handle.destroy();
          load(record.ID, clone);
        }}
      />,
      onOk() {
        if (!selected) return false;
        load(selected.ID, clone);
      },
    });
  }

  function load(id, clone = false) {
    if (!id) return;
    Promise.all([
      client.job('CardR', {
        Card: 'B.流程定义',
        Model: null,
        Filter: [['ID', '=', id]],
      }),
      client.job('CardR', {
        Card: 'B.流程节点',
        Model: null,
        Filter: [['流程定义', '=', id]],
      }),
      client.job('CardR', {
        Card: 'B.流程路径',
        Model: null,
        Filter: [['流程定义', '=', id]],
      })]).then(([{ Rows: [wf] }, { Rows: nodes }, { Rows: edges }]) => {
        data_ref.current = clone ? { nodes: [], edges: [] } : { nodes, edges };
        // 二次加载名称
        Promise.all([
          client.job('CardR', {
            Card: '卡片',
            Model: ['名称'],
            Filter: [['ID', '=', wf.数据卡片]]
          }),
          client.job('CardR', {
            Card: '子系统页面',
            Model: ['名称'],
            Filter: [['ID', 'in', [wf.默认办理页面, ...nodes.map(n => n.办理页面)]]]
          }),
          !clone ? Promise.resolve({ Rows: [] }) : client.job('CardR', {
            Card: 'B.流程定义',
            Model: ['名称', '版本号'],
            Filter: [['名称', '=', wf.名称]]
          })]).then(([{ Rows: [card] }, { Rows: pages }, { Rows: vers }]) => {
            // 增加名称字段
            const map = {};
            pages.forEach(p => map[p.ID] = p.名称);
            wf.数据卡片名称 = card.名称;
            wf.默认办理页面名称 = map[wf.默认办理页面];
            nodes.forEach(n => n.办理页面名称 = map[n.办理页面]);
            if (clone) {
              delete wf.ID;
              wf.版本号 = vers.reduce((prev, v) => (prev < v.版本号 ? v.版本号 : prev), 1) + 1;
              wf.启用标记 = 0;
            }

            const cells = [];

            nodes.forEach(node => {
              cells.push({
                "id": node.ID,
                "shape": "node-" + node.类型,
                "label": node.名称,
                "position": {
                  "x": node.坐标X,
                  "y": node.坐标Y,
                },
                "size": {
                  "width": node.宽度,
                  "height": node.高度,
                },
                "data": clone ? { ...node, ID: undefined } : node,
                "ports": {
                  "groups": {
                    "top": {
                      "position": "top",
                      "attrs": {
                        "circle": {
                          "r": 4,
                          "magnet": true,
                          "stroke": "#5F95FF",
                          "strokeWidth": 1,
                          "fill": "#fff",
                          "style": {
                            "visibility": "hidden"
                          }
                        }
                      }
                    },
                    "right": {
                      "position": "right",
                      "attrs": {
                        "circle": {
                          "r": 4,
                          "magnet": true,
                          "stroke": "#5F95FF",
                          "strokeWidth": 1,
                          "fill": "#fff",
                          "style": {
                            "visibility": "hidden"
                          }
                        }
                      }
                    },
                    "bottom": {
                      "position": "bottom",
                      "attrs": {
                        "circle": {
                          "r": 4,
                          "magnet": true,
                          "stroke": "#5F95FF",
                          "strokeWidth": 1,
                          "fill": "#fff",
                          "style": {
                            "visibility": "hidden"
                          }
                        }
                      }
                    },
                    "left": {
                      "position": "left",
                      "attrs": {
                        "circle": {
                          "r": 4,
                          "magnet": true,
                          "stroke": "#5F95FF",
                          "strokeWidth": 1,
                          "fill": "#fff",
                          "style": {
                            "visibility": "hidden"
                          }
                        }
                      }
                    }
                  },
                  "items": [
                    {
                      "group": "top",
                      "id": node.ID + '-0'
                    },
                    {
                      "group": "right",
                      "id": node.ID + '-1'
                    },
                    {
                      "group": "bottom",
                      "id": node.ID + '-2'
                    },
                    {
                      "group": "left",
                      "id": node.ID + '-3'
                    }
                  ]
                },
              });
            });

            edges.forEach(edge => {
              cells.push({
                "id": edge.ID,
                "shape": "edge",
                "attrs": {
                  "line": {
                    "stroke": "#A2B1C3",
                    "targetMarker": {
                      "name": "block",
                      "width": 12,
                      "height": 8
                    }
                  }
                },
                "defaultLabel": {
                  "markup": [
                    {
                      "tagName": "rect",
                      "selector": "body"
                    },
                    {
                      "tagName": "text",
                      "selector": "label"
                    }
                  ],
                  "attrs": {
                    "text": {
                      "fill": "#262626",
                      "fontSize": 12,
                      "textAnchor": "middle",
                      "textVerticalAnchor": "middle",
                      "pointerEvents": "none"
                    },
                    "rect": {
                      "ref": "label",
                      "fill": "transparent",
                      "rx": 3,
                      "ry": 3,
                      "refWidth": 1,
                      "refHeight": 1,
                      "refX": 0,
                      "refY": 0
                    }
                  },
                  "position": {
                    "distance": edge.标签偏移 ?? 0.5,
                    "offset": -10,
                    "options": {
                      "keepGradient": true
                    }
                  }
                },
                "label": edge.名称,
                "data": clone ? { ...edge, ID: undefined } : edge,
                "source": {
                  "cell": edge.入口节点,
                  "port": edge.入口节点 + "-" + edge.入口锚点
                },
                "target": {
                  "cell": edge.出口节点,
                  "port": edge.出口节点 + "-" + edge.出口锚点
                }
              });
            });

            graph_ref.current.cleanHistory();
            graph_ref.current.fromJSON({ cells });
            graph_ref.current.centerContent();

            form_workflow.resetFields();
            form_workflow.setFieldsValue(wf);
          });
      });
  }

  function save() {
    form_workflow.validateFields().then(values => {
      client.job('CardW', {
        Card: 'B.流程定义',
        Model: null,
        Record: values
      }).then(([{ record }]) => {
        const { cells } = graph_ref.current.toJSON();
        // 先处理节点
        const nodes = [];
        cells.filter(c => c.shape !== 'edge').forEach(cell => {
          nodes[cell.id] = {
            ...cell.data,
            ID: cell.data.ID ?? uuidv4().replace(/-/g, ''),
            流程定义: record.ID,
            坐标X: cell.position.x,
            坐标Y: cell.position.y,
            宽度: cell.size.width,
            高度: cell.size.height,
          };
          nodes.push(nodes[cell.id]);
        });
        // 后处理路径
        const edges = [];
        cells.filter(c => c.shape === 'edge').forEach(cell => {
          const source = graph_ref.current.getCellById(cell.source.cell);
          const target = graph_ref.current.getCellById(cell.target.cell);
          edges.push({
            ...cell.data,
            流程定义: record.ID,
            入口节点: nodes[cell.source.cell].ID,
            出口节点: nodes[cell.target.cell].ID,
            入口锚点: source.getPortIndex(cell.source.port),
            出口锚点: target.getPortIndex(cell.target.port),
          });
        });

        client.job('CardDW', {
          D: [{
            Card: 'B.流程节点',
            Records: data_ref.current.nodes
          }, {
            Card: 'B.流程路径',
            Records: data_ref.current.edges
          }],
          W: [{
            Card: 'B.流程节点',
            Model: null,
            Records: nodes
          }, {
            Card: 'B.流程路径',
            Model: null,
            Records: edges
          }]
        }).then(() => {
          alert("保存成功");
          // 重新加载
          load(record.ID);
        });
      });
    });
  }

  function remove() {
    modal.open({
      title: '删除确认',
      content: '确定要删除当前版本的流程吗?',
      async onOk() {
        const { Total } = await client.job('CardR', {
          Card: 'B.流程实例',
          Model: ['流程定义'],
          Filter: [['流程定义', '=', wf_ID]],
          PageSize: 10,
        });
        if (Total > 0) {
          alert("当前版本已存在实例，无法删除");
        } else {
          await client.job('CardD', {
            ID: wf_ID,
            Card: 'B.流程定义',
            Children: {
              'B.流程节点': '流程定义',
              'B.流程路径': '流程定义',
            }
          });
          alert("删除成功");
          reset();
        }
      }
    });
  }

  function import_json() {
    let json = null;
    modal.open({
      title: '导入JSON数据',
      content: <Input.TextArea autoSize={{ minRows: 5, maxRows: 10 }} onChange={e => json = e.target.value} />,
      onOk() {
        graph_ref.current.cleanHistory();
        graph_ref.current.fromJSON(JSON.parse(json));
        graph_ref.current.centerContent();
      }
    });
  }

  function export_json() {
    modal.open({
      title: '导出JSON数据',
      content: <Input.TextArea autoSize={{ minRows: 5, maxRows: 10 }} value={JSON.stringify(graph_ref.current.toJSON())} />
    });
  }

  return (
    <div className={styles.editor}>
      <div className={styles.toolbar} style={{ height: token.controlHeight }}>
        <Space>
          <Button type="text" icon={<span className="iconfont mp-file-add"></span>} title="新建"
            onClick={reset} />
          <Button type="text" icon={<span className="iconfont mp-folder-open"></span>} title="打开"
            onClick={() => open()} />
          <Button type="text" icon={<span className="iconfont mp-copy"></span>} title="克隆新版本"
            onClick={() => open(true)} />
          <Button type="text" icon={<span className="iconfont mp-save"></span>} title="保存"
            onClick={save} disabled={!wf_名称 || !wf_数据卡片} />
          <Button type="text" danger icon={<span className="iconfont mp-delete1"></span>} title="删除"
            onClick={remove} disabled={!wf_ID} />
          <Divider type="vertical" />
          <Button type="text" icon={<span className="iconfont mp-undo"></span>} title="撤销"
            disabled={!state.canUndo} onClick={() => graph_ref.current.undo()} />
          <Button type="text" icon={<span className="iconfont mp-redo"></span>} title="重做"
            disabled={!state.canRedo} onClick={() => graph_ref.current.redo()} />
          <Divider type="vertical" />
          <Button type="text" icon={<span className="iconfont mp-icon-test"></span>} title="缩放至100%"
            onClick={() => graph_ref.current.zoomTo(1)} />
          <Button type="text" icon={<span className="iconfont mp-pic-center"></span>} title="居中"
            onClick={() => graph_ref.current.centerContent()} />
          <Divider type="vertical" />
          <Button type="text" icon={<span className="iconfont mp-Import"></span>} title="导入"
            onClick={import_json} />
          <Button type="text" icon={<span className="iconfont mp-export"></span>} title="导出"
            onClick={export_json} />
        </Space>
      </div>
      <div className={styles.main}>
        <div className={styles.stencil} ref={stencil_container}></div>
        <div className={styles.content} ref={content_container}></div>
        <div className={styles.panel}>
          <div hidden={state.selected}>
            <div className={styles.title}>流程</div>
            <Form form={form_workflow} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}>
              <Form.Item name="ID" hidden>
                <Input />
              </Form.Item>
              <Form.Item name="名称" label="名称" rules={[{ required: true }]}>
                <Input />
              </Form.Item>
              <Form.Item name="版本号" label="版本号" initialValue={1}>
                <Input disabled />
              </Form.Item>
              <Form.Item name="启用标记" label="启用标记" initialValue={false} valuePropName="checked">
                <Checkbox />
              </Form.Item>
              <Form.Item name="数据卡片名称" label="数据卡片" rules={[{ required: true }]}>
                <PopupSelect title="请选择卡片" field="名称" placeholder='搜索卡片名称'
                  columns={[{
                    title: '开发包',
                    dataIndex: '开发包.名称',
                    width: 150,
                  }, {
                    title: '名称',
                    dataIndex: '名称',
                    width: 150,
                  }, {
                    title: '描述',
                    dataIndex: '描述',
                    width: 150,
                  }]}
                  onLoad={(queryValue, queryOrder, pageNo, pageSize) => {
                    return client.job('CardR', {
                      Card: '卡片',
                      Model: ['开发包.名称', '名称', '描述'],
                      Filter: [['名称', 'like', queryValue]],
                      Order: { '开发包.名称': 'asc', '名称': 'asc' },
                      PageSize: pageSize,
                      PageNo: pageNo,
                    });
                  }}
                  onChange={(_, record) => {
                    if (record) {
                      form_workflow.setFieldsValue({
                        数据卡片: record.ID,
                        数据卡片名称: record.名称,
                      });
                    } else {
                      form_workflow.setFieldValue('数据卡片', undefined);
                    }
                  }} />
              </Form.Item>
              <Form.Item name="数据卡片" hidden>
                <Input />
              </Form.Item>
              <Form.Item name="匹配条件脚本" label="匹配条件脚本">
                <JavascriptInput title="匹配条件脚本: ({UID, record}) => Boolean|null|undefined" template={TEMPLATE_SCRIPT} />
              </Form.Item>
              <Divider>默认值</Divider>
              <Form.Item name="默认办理页面名称" label="默认办理页面">
                <PopupSelect title="请选择页面" field="名称" placeholder='搜索页面名称'
                  columns={[{
                    title: '子系统',
                    dataIndex: '子系统.名称',
                    width: 150,
                  }, {
                    title: '名称',
                    dataIndex: '名称',
                    width: 150,
                  }, {
                    title: '描述',
                    dataIndex: '描述',
                    width: 150,
                  }]}
                  onLoad={(queryValue, queryOrder, pageNo, pageSize) => {
                    return client.job('CardR', {
                      Card: '子系统页面',
                      Model: ['子系统.名称', '名称', '描述'],
                      Filter: [['名称', 'like', queryValue]],
                      Order: { '子系统.名称': 'asc', '名称': 'asc' },
                      PageSize: pageSize,
                      PageNo: pageNo,
                    });
                  }}
                  onChange={(_, record) => {
                    if (record) {
                      form_workflow.setFieldsValue({
                        默认办理页面: record.ID,
                        默认办理页面名称: record.名称,
                      });
                    } else {
                      form_workflow.setFieldValue('默认办理页面', undefined);
                    }
                  }} />
              </Form.Item>
              <Form.Item name="默认办理页面" hidden>
                <Input />
              </Form.Item>
              <Form.Item name="待办描述脚本" label="待办描述脚本">
                <JavascriptInput title="待办描述脚本: ({UID, workflow, instance, record}) => String" template={TEMPLATE_SCRIPT} />
              </Form.Item>
            </Form>
          </div>
          {
            state.selected && state.selected.isNode() ? (
              <div>
                <div className={styles.title}>节点</div>
                <Form form={form_node} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} disabled={state.selected.data.类型 === 5}
                  onValuesChange={(changes, all) => Object.assign(state.selected.data, all)}>
                  <Form.Item name="名称" label="名称" rules={[{ required: true }]}>
                    <Input onChange={e => state.selected.setAttrs({ label: { text: e.target.value } })} />
                  </Form.Item>
                  <Form.Item name="状态描述" label="状态描述" rules={[{ required: true }]}>
                    <Input />
                  </Form.Item>
                  <Form.Item name="类型" label="类型">
                    <Select disabled>
                      <Select.Option value={1}>开始</Select.Option>
                      <Select.Option value={2}>结束</Select.Option>
                      <Select.Option value={3}>审批</Select.Option>
                      <Select.Option value={4}>会签</Select.Option>
                      <Select.Option value={5}>虚拟</Select.Option>
                    </Select>
                  </Form.Item>
                  <Form.Item name="办理页面名称" label="办理页面">
                    <PopupSelect title="请选择页面" field="名称" placeholder='搜索页面名称'
                      disabled={state.selected.data.类型 === 2 || state.selected.data.类型 === 5}
                      columns={[{
                        title: '子系统',
                        dataIndex: '子系统.名称',
                        width: 150,
                      }, {
                        title: '名称',
                        dataIndex: '名称',
                        width: 150,
                      }, {
                        title: '描述',
                        dataIndex: '描述',
                        width: 150,
                      }]}
                      onLoad={(queryValue, queryOrder, pageNo, pageSize) => {
                        return client.job('CardR', {
                          Card: '子系统页面',
                          Model: ['子系统.名称', '名称', '描述'],
                          Filter: [['名称', 'like', queryValue]],
                          Order: { '子系统.名称': 'asc', '名称': 'asc' },
                          PageSize: pageSize,
                          PageNo: pageNo,
                        });
                      }}
                      onChange={(_, record) => {
                        if (record) {
                          form_node.setFieldsValue({
                            办理页面: record.ID,
                            办理页面名称: record.名称,
                          });
                        } else {
                          form_node.setFieldValue('办理页面', undefined);
                        }
                      }} />
                  </Form.Item>
                  <Form.Item name="办理页面" hidden>
                    <Input />
                  </Form.Item>
                  <Divider>脚本</Divider>
                  <Form.Item name="待办描述脚本" label="待办描述脚本">
                    <JavascriptInput title="待办描述脚本: ({UID, workflow, instance, record}) => String" template={TEMPLATE_SCRIPT}
                      disabled={state.selected.data.类型 === 2 || state.selected.data.类型 === 5} />
                  </Form.Item>
                  <Form.Item name="待办用户脚本" label="待办用户脚本">
                    <JavascriptInput title="待办用户脚本: ({UID, workflow, instance, record}) => [String]" template={TEMPLATE_SCRIPT}
                      disabled={state.selected.data.类型 === 1 || state.selected.data.类型 === 2 || state.selected.data.类型 === 5} />
                  </Form.Item>
                  <Form.Item name="流入脚本" label="流入脚本">
                    <JavascriptInput title="流入脚本: ({UID, workflow, instance, record, leave, arrive, action, forward, userIds}) => void" template={TEMPLATE_SCRIPT}
                      disabled={state.selected.data.类型 === 5} />
                  </Form.Item>
                  <Form.Item name="流出脚本" label="流出脚本">
                    <JavascriptInput title="流出脚本: ({UID, workflow, instance, record, leave, arrive, action, forward}) => void" template={TEMPLATE_SCRIPT}
                      disabled={state.selected.data.类型 === 2 || state.selected.data.类型 === 5} />
                  </Form.Item>
                  <Divider>其他</Divider>
                  <Form.Item name="二审自动通过" label="二审自动通过" initialValue={false} valuePropName="checked">
                    <Checkbox disabled={state.selected.data.类型 === 1 || state.selected.data.类型 === 2 || state.selected.data.类型 === 5} />
                  </Form.Item>
                  <Form.Item name="会签通过率" label="会签通过率" hidden={state.selected.data.类型 !== 4}>
                    <InputNumber step={0.1} min={0} max={1} addonAfter="%" formatter={v => v ? v * 100 : v} parser={v => v ? v / 100 : v} />
                  </Form.Item>
                </Form>
              </div>
            ) : null
          }
          {
            state.selected && state.selected.isEdge() ? (
              <div>
                <div className={styles.title}>路径</div>
                <Form form={form_edge} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}
                  onValuesChange={(changes, all) => Object.assign(state.selected.data, all)}>
                  <Form.Item name="名称" label="名称">
                    <Input onChange={e => {
                      const [label] = state.selected.getLabels();
                      state.selected.setLabels({ ...label, attrs: { text: { text: e.target.value } } });
                    }} />
                  </Form.Item>
                  <Form.Item name="标签偏移" label="标签偏移" initialValue={0.5}>
                    <InputNumber step={0.1} min={0} max={1} addonAfter="%" formatter={v => v ? v * 100 : v} parser={v => v ? v / 100 : v}
                      onChange={v => {
                        const [label] = state.selected.getLabels();
                        state.selected.setLabels({ ...label, position: { distance: v } });
                      }} />
                  </Form.Item>
                  <Form.Item name="通行条件脚本" label="通行条件脚本">
                    <JavascriptInput title="通行条件脚本: ({UID, workflow, instance, record}) => Number|Boolean|null|undefined" template={TEMPLATE_SCRIPT} />
                  </Form.Item>
                </Form>
              </div>
            ) : null
          }
        </div>
        <div className={styles.tips}>按住Ctrl可以拖动画布</div>
      </div>
      {modalHolder}
    </div>
  );
}

function JavascriptInput({ title, template, value, onChange, ...props }) {
  const [modal, modalHolder] = useModal();
  let _value = value ?? template;
  return <>
    <Input allowClear addonAfter={<EditOutlined />} value={value} {...props}
      onClick={() => modal.open({
        title,
        width: '80%',
        content: <CodeMirror value={_value} onChange={v => _value = v} height="60vh" extensions={[javascript()]} />,
        onOk() {
          onChange(_value);
        }
      })}
      onChange={e => {
        // allowClear的处理
        if (e.target.value === '') {
          onChange('');
        }
      }} />
    {modalHolder}
  </>;
}